//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Client = {
	session: {},

	timeDif: 0,

	avoidCacheBase: globals.torams(),
	avoidCacheAjaxDif: 0,
	avoidCacheAjax: function() {
		return '?AC=' + Client.avoidCacheBase + ':' + (Client.avoidCacheAjaxDif++) +
			'&TD=' + Client.timeDif;
	},

	avoidCachePhotoDif: 0,
	avoidCachePhoto: function() {
		return '?AC=' + Client.avoidCacheBase + ':' + Client.avoidCachePhotoDif;
	},

	color: {
		ok: '#000000',
		error: '#DD0000'
	},

	// Το property "paraskinioWindow" τίθεται κάθε φορά που ανοίγουμε
	// παράθυρο επιλογής παρασκηνίου να δείχνει το ίδιο το παράθυρο,
	// ώστε να μπορούμε να το κλείσουμε όταν χρειαστεί.

	paraskinioWindow: null,

	// Ο δείκτης "anadisiIndex" βοηθάει στην ανάδειξη DOM elements σε
	// πρώτο πλάνο. Κάθε φορά που αναδεικνύεται κάποιο DOM element σε
	// πρώτο πλάνο, αυτός ο δείκτης αυξάνει κατά ένα.

	anadisiIndex: 100,

	// Ακολουθεί το πλάτος της φωτογραφίας προφίλ του παίκτη όπως
	// αυτή εμφανίζεται με κλικ στο ανθρωπάκι του login section
	// και της προφίλ καρτέλας.

	photoPektisWidth: '180px',
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Client.isPektis = function() {
	return Client.session.pektis;
}

Client.oxiPektis = function() {
	return !Client.isPektis();
}

Client.isKlidi = function() {
	return Client.session.klidi;
}

Client.oxiKlidi = function() {
	return !Client.isKlidi();
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Η μέθοδος "pektisPhotofile" δέχεται το login name του παίκτη και επιστρέφει
// το πλήρες URL της σχετικής φωτογραφίας προφίλ. Θυμίζω ότι τα σχετικά files
// βρίσκονται κάτω από το directory "photo" και μάλιστα σε subdirectories που
// ονοματίζονται από το πρώτο γράμμα του login name (lowercase), π.χ. το file
// της φωτογραφίας του παίκτη "panos" βρίσκεται στο "photo/p/panos.jpg". Όλα
// τα σχετικά files φέρουν παρέκταμα ".jpg", ασχέτως αν είναι άλλου τύπου, π.χ.
// gif, png κλπ.
//
// Για να αποφύγουμε την απονέκρωση των εικόνων λόγω caching, προσθέτουμε στο
// URL μεταβλητές που αλλάζουν κάθε φορά που εκκινεί το πρόγραμμα. Επίσης,
// μπορούμε να προκαλέσουμε αλλαγή μέσω της μεθόδου "pektisPhotoRefresh" που
// ακολουθεί.

Client.pektisPhotofile = function(pektis, fresh) {
	if (!pektis) return Client.missingPhoto;
	if (fresh) Client.pektisPhotoRefresh();
	return Client.server + 'photo/' + pektis.substr(0, 1).toLowerCase() +
		'/' + pektis + '.jpg' + Client.avoidCachePhoto();
}

Client.pektisPhotoRefresh = function() {
	Client.avoidCachePhotoDif++;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Η μέθοδος "isodos" καλείται αμέσως μετά την επιτυχή είσοδο ή εγγραφή μας στο καφενείο.
// Η συνεδρία έχει εισαχθεί στην database, τα session data έχουν ήδη ενημερωθεί και σκοπός
// της μεθόδου είναι να ενημερωθεί το σκηνικό του server και κατ' επέκτασιν των clients με
// τη νέα συνεδρία. Η μέθοδος δέχεται ως παραμέτρους το login name του παίκτη, το κλειδί
// και δύο functions που καλούνται σε περίπτωση επιτυχίας και αποτυχίας αντίστοιχα. Επιτυχία
// θεωρείται η μη επιστροφή οποιωνδήποτε δεδομένων, ενώ αντίστροφα αν επιστραφούν δεδομένα,
// θεωρούνται μηνύματα λάθους.

Client.sodosi = false;

Client.isodos = function(login, klidi, onsuccess, onerror) {
	if (Client.sodosi) return;
	Client.sodosi = true;

	delete Client.session.pektis;
	delete Client.session.klidi;
	Client.node('sinedriaNea', 'pektis=' + login.uri(), 'klidi=' + klidi.uri(), function(rsp) {
		Client.sodosi = false;
		Client.fyi.spano(rsp);
		if (rsp) {
			if (onerror) onerror(login, klidi, rsp);
			return;
		}

		if (onsuccess) onsuccess(login, klidi);
	});
}

// Η μέθοδος "exodos" είναι πιο γενική και καλείται για την έξοδό μας από το καφενείο.
// Αφού δώσουμε ένδειξεη εξόδου στο tab "Έξοδος", προχωρούμε σε διαγραφή της συνεδρίας
// από την database και διαγραφή των διαπιστευτηρίων session data. Αν όλα αυτά γίνουν
// επιτυχώς προχωρούμε σε ενημέρωση του σκηνικού του server και κατ' επέκτασιν των
// clients.

Client.sodoxe = false;

Client.exodos = function() {
	if (Client.sodoxe) return;
	Client.sodoxe = true;

	Client.fyi.pano('Έξοδος από το καφενείο. Παρακαλώ περιμένετε…');
	var x = $('#exodos');
	var exodos_html = x.html();
	if (x.length) {
		var w = x.innerWidth();
		var h = parseInt(x.innerHeight() * 2 / 3);
		x.html($('<img src = "' + Client.server + 'images/working/bares.gif" />').css({
			width: w + 'px',
			height: h + 'px',
		}));
	}

	Client.ajax('misc/exodos', function(rsp) {
		if (rsp) {
			Client.fyi.spano(rsp);
			$('#exodos').html(exodos_html);
			Client.sodoxe = false;
			return;
		}

		try { Client.node('sinedriaExodos', false); } catch(e) {};
		window.location = Client.server;
	});

	return false;
}

Client.accountData = function() {
	if (Client.paraskinioWindow) Client.paraskinioWindow.Paraskinio.telos();
}

// Η μέθοδος "paraskinio" καλείται είτε όταν θέλουμε να ανοίξουμε το παράθυρο
// επιλογής παρασκηνίου, είτε όταν θέλουμε να κλείσουμε αυτό το παράθυρο.
// Το ποια από τις δύο ενέργειες θα εκτελεστεί το αποφασίζουμε από το global
// αντικείμενο "Client.paraskinioWindow" που είναι ακριβώς το παράθυρο επιλογής
// παρασκηνίου.

Client.paraskinio = function(ico, id) {
	$(ico).working();
	if (Client.paraskinioWindow) {
		setTimeout(function() {
			Client.paraskinioWindow.close();
			Client.paraskinioWindow = null;
			$(ico).removeClass('iconButtonIpodixi').
				attr({src: Client.server + 'images/cpanel/paraskinio.png'});
		}, 200);
		return;
	}

	if (document.all) {
		var t = window.screenTop;
		var l = window.screenLeft;
	}
	else {
		t = window.screenY;
		l = window.screenX;
	}

	setTimeout(function() {
		var selida = Client.server + 'paraskinio/index.php?parentIconId=' + id.uri();
		Client.paraskinioWindow = window.open(selida, 'paraskinio',
			'top=' + (t + 350) + ',left=' + (l + 100) +
			',status=1,width=850,height=450,resizeable=1');
		$('#' + id).addClass('iconButtonIpodixi').attr({
			src: Client.server + 'images/cpanel/paraskinio.png'
		});
	}, 10);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Client.fyi = {
	pano: function(s, t, ixos) {
		Client.fyi.print(s, 'fyiPano', t);
		if (ixos) Client.sound.play(ixos);
	},

	kato: function(s, t, ixos) {
		Client.fyi.print(s, 'fyiKato', t);
		if (ixos) Client.sound.play(ixos);
	},

	epano: function(s, t, ixos) {
		if (s) {
			s = '<span class="lathos">' + s + '</span>';
			if (ixos) {
				if (ixos === true) Client.sound.error();
				else Client.sound.play(ixos);
			}
		}

		Client.fyi.pano(s, t);
	},

	spano: function(s, t) {
		Client.fyi.epano(s, t, true);
	},

	ekato: function(s, t, ixos) {
		if (s) {
			s = '<span class="lathos">' + s + '</span>';
			if (ixos) {
				if (ixos === true) Client.sound.error();
				else Client.sound.play(ixos);
			}
		}

		Client.fyi.kato(s, t);
	},

	skato: function(s, t) {
		Client.fyi.ekato(s, t, true);
	},

	timer: {},

	print: function(s, pk, t) {
		if (Client.fyi.timer[pk]) {
			clearTimeout(Client.fyi.timer[pk]);
			Client.fyi.timer[pk] = null;
		}

		var f = $('#' + pk);
		if (!f.length) return;

		if (!t) t = 6;
		if (s) {
			f.stop().html(s).css('visibility', 'visible').fadeTo(0, 1);
			Client.fyi.timer[pk] = setTimeout(function() {
				$(f).stop().fadeTo(1000, 0);
			}, t * 1000);
		}
		else {
			f.stop().fadeTo(200, 0, function() {
				$(this).html('').css('visibility', 'hidden');
			});
		}
	}
};

Client.sound = {
	entasi: {
		'ΣΙΩΠΗΛΟ': 0,
		'ΧΑΜΗΛΗ': 3,
		'ΚΑΝΟΝΙΚΗ': 6,
		'ΔΥΝΑΤΗ': 10,
	},

	miosi: {
		'tsalakoma.ogg': 0.4,
	},

	play: function(sound, vol, delay) {
		var entasi, ixos;

		if (!Client.sound.entasi.hasOwnProperty(Client.session.entasi)) Client.session.entasi = 'ΚΑΝΟΝΙΚΗ';
		entasi = Client.sound.entasi[Client.session.entasi];
		if (entasi < 1) return;

		if (vol === undefined) vol = Client.sound.entasi['ΚΑΝΟΝΙΚΗ'];
		else if (vol === null) vol = Client.sound.entasi['ΚΑΝΟΝΙΚΗ'];
		else if (vol <= 0) return;
		else if (vol > Client.sound.entasi['ΔΥΝΑΤΗ']) vol = Client.sound.entasi['ΔΥΝΑΤΗ'];
		if (Client.sound.miosi.hasOwnProperty(sound)) vol *= Client.sound.miosi[sound];

		ixos = $('#ixos').akrivosEna();
		$('<audio src="' + Client.server + 'sounds/' + sound + '" />').appendTo(ixos).each(function() {
			var audio = this;
			audio.volume = (entasi / Client.sound.entasi['ΔΥΝΑΤΗ']) * (vol / Client.sound.entasi['ΔΥΝΑΤΗ']);
			setTimeout(function() { audio.play(); }, delay ? delay : 1);
		});

		if (ixos.children().size() > 100) ixos.children(':lt(30)').remove();
	},

	errorLast: 0,

	error: function(sound, vol) {
		Client.sound.play(sound ? sound : 'tilt.ogg', vol);
	},

	tap: function(vol) {
		Client.sound.play('tap.ogg', vol);
	},

	beep: function(vol) {
		Client.sound.play('beep.ogg', vol);
	},

	tilt: function(vol) {
		Client.sound.play('tilt.ogg', vol);
	},

	pop: function(vol) {
		Client.sound.play('pop.ogg', vol);
	},

	plop: function(vol) {
		Client.sound.play('plop.ogg', vol);
	},

	blioup: function(vol) {
		Client.sound.play('blioup.ogg', vol);
	},

	doorbell: function(vol) {
		Client.sound.play('doorBell.ogg', vol);
	},

	dialogos: function(vol) {
		if (vol === undefined) vol = 4;
		Client.sound.play('bop.ogg', vol);
	},

	stisimoEndixi: function(vol) {
		if (vol === undefined) vol = 6;
		Client.sound.play('tik.wav', vol);
	},

	trapeziNeo: function(vol) {
		if (vol === undefined) vol = 6;
		Client.sound.play('pop.ogg', vol);
	},

	trapeziDel: function(vol) {
		if (vol === undefined) vol = 6;
		Client.sound.play('blioup.ogg', vol);
	},

	dianomi: function(vol) {
		if (vol === undefined) vol = 6;
		Client.sound.play('tic.ogg', vol);
	},

	thesiTheasis: function(vol) {
		if (vol === undefined) vol = 6;
		Client.sound.play('tic.ogg', vol);
	},

	skisimo: function(vol) {
		if (vol === undefined) vol = Client.sound.entasi['ΚΑΝΟΝΙΚΗ'];
		Client.sound.play('skisimo.ogg', vol);
	},

	// Ακολουθεί ήχος που παράγεται όταν ο τζογαδόρος ακυρώνει ή θέτει
	// τυχόν άσους που έχει στην αγορά του.

	protasiAsoi: function(nai, vol) {
		if (nai === undefined) nai = true;
		if (vol === undefined) vol = Client.sound.entasi['ΧΑΜΗΛΗ'];
		Client.sound.play(nai ? 'ding.ogg' : 'tak.ogg', vol);
	},

	solo: function() {
		Client.sound.play('kanonia.ogg', Client.sound.entasi['ΔΥΝΑΤΗ']);
	},

	agora: function() {
		Client.sound.play('bikebell.ogg');
	},
};

Client.session.entasi = 'ΚΑΝΟΝΙΚΗ';

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

jQuery.fn.tsalimi = function(n, t) {
	var jql = $(this);

	if (n === undefined) n = 10;
	if (t === undefined) t = 150;

	jql.removeClass('tsalimiPano tsalimiKato');
	if (n <= 0) return;

	jql.addClass(n % 2 ? 'tsalimiPano' : 'tsalimiKato');
	setTimeout(function() {
		jql.tsalimi(n - 1, t);
	}, t);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Με την μέθοδο που ακολουθεί μπορούμε να θέσουμε session cookie values.
// Περνάμε το όνομα του cookie και την τιμή, ενώ αν θέλουμε να καθαρίσουμε
// κάποιο cookie περνάμε μόνο το όνομα χωρίς παράμετρο τιμής.

Client.setCookie = function(tag, val) {
	var params = 'tag=' + tag.uri();
	if ((val !== undefined) && (val !== null)) params += '&val=' + val.uri();

	Client.ajax('misc/setCookie', params);
}

// Η μέθοδος "setPeparam" δέχεται ως παραμέτρους το είδος και την τιμή παραμέτρου
// παίκτη και ενημερώνει ανάλογα την συγκεκριμένη παράμετρο του τρέχοντος παίκτη.
// Μπορούμε να δώσουμε ως τρίτη παράμετρο μια callback function η οποία θα κληθεί
// κατά την επιστροφή, με παράμετρο το output του προγράμματος ενημέρωσης το οποίο
// πρέπει να είναι κενό.

Client.setPeparam = function(param, timi, callback) {
	Client.ajax('misc/setPeparam', 'param=' + param.uri(), 'timi=' + timi.uri(), callback);
}

Client.stopProp = function(e) {
	if (e === false) return;
	if (!e) e = window.event;
	if (!e) return;
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();
}

Client.controlKey = function(e) {
	if (!e) e = window.event;
	if (e && e.ctrlKey) return true;
	if (e.which == 17) return true;
	if (e.which == 13) return true;
	return false;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Η μέθοδος "ajax" δρομολογεί κλήση Ajax και δέχεται ως πρώτη παράμετρο
// το php πρόγραμμα το οποίο θα απαντήσει το αίτημα. Η κλήση γίνεται ως εξής:
//
//	Client.ajax(service [{, data}...] [, callback ] [, asynch ])
//
// service	url php προγράμματος που θα διαχειριστεί το αίτημα και
//		θα στείλει απάντηση.
//
// data		string παράμετρος της μορφής "key=value". Μπορούν να
//		δοθούν πολλές τέτοιες παράμετροι και περνάνε με POST.
//
// callback	function που θα κληθεί με την επιστροφή της απάντησης.
//		Δέχεται ως παράμετρο τα επιστρεφόμενα data.
//
// asynch	true/false (default true) που δείχνει αν το αίτημα θα
//		εξυπηρετηθεί ασύγχρονα ή σύγχρονα.

Client.ajax = function() {
	var sdca = Client.xhrSdca(arguments, 'Ajax');
	var xhr = Client.xhrGet(sdca);

	var service = Client.server + sdca.service + '.php' + Client.avoidCacheAjax();
	if (Client.isPektis()) sdca.data += '&pektis=' + Client.session.pektis.uri();

	xhr.open('POST', service, sdca.asynch);
	xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
	if (sdca.data) xhr.send(sdca.data); else xhr.send();

	if (sdca.asynch) return;
	if (sdca.callback) return sdca.callback(Client.xhrResponse(xhr, sdca));
	return Client.xhrResponse(xhr, sdca);
}

// Η μέθοδος "node" δρομολογεί κλήση Ajax προς τον Node server. Ως πρώτη
// παράμετρο περνάμε το όνομα του service το οποίο θα απαντήσει το αίτημα.
// Η κλήση γίνεται ως εξής:
//
//	Client.node(service [{, data}...] [, callback ] [, asynch ])

Client.node = function() {
	var sdca = Client.xhrSdca(arguments, 'Node');
	var xhr = Client.xhrGet(sdca);

	sdca.service = Client.nodeServer + globals.servicePort + '/' + sdca.service + Client.avoidCacheAjax();
	if (Client.isPektis()) sdca.service += '&pektis=' + Client.session.pektis.uri();
	if (Client.isKlidi()) sdca.service += '&klidi=' + Client.session.klidi.uri();
	if (sdca.data) sdca.service += sdca.data;

	xhr.open('GET', sdca.service, sdca.asynch);
	xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
	xhr.send();

	if (sdca.asynch) return;
	if (sdca.callback) return sdca.callback(Client.xhrResponse(xhr, sdca));
	return Client.xhrResponse(xhr, sdca);
}

Client.xhrSdca = function(args, idos) {
	var sdca = {};
	sdca.service = args[0];
	sdca.data = '';
	sdca.callback = false;
	sdca.asynch = true;
	sdca.idos = idos;

	for (var i = 1; i < args.length; i++) {
		switch (typeof args[i]) {
		case 'function':
		case 'object':
			sdca.callback = args[i];
			break;
		case 'string':
			sdca.data += '&' + args[i];
			break;
		case 'undefined':
		case 'null':
			break;
		default:
			sdca.asynch = args[i];
			break;
		}
	}

	return sdca;
}

Client.xhrGet = function(sdca) {
	try { var xhr = new XMLHttpRequest(); } catch(e) {
		try { xhr = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) {
			try { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) {
				globals.fatal('xhrGet: failed');
			}
		}
	}

	if (sdca.asynch && sdca.callback) {
		xhr.onreadystatechange = function() {
			if (xhr.readyState != 4) return;
			sdca.callback(Client.xhrResponse(xhr, sdca));
		}
	}

	return xhr;
}

Client.xhrResponse = function(xhr, sdca) {
	var err = false;
	if (!xhr) err = 'undefined ' + sdca.idos + ' request';
	else if (!xhr.status) err = 'Ο server δεν ανταποκρίνεται (' + sdca.idos + ')';
	else if (xhr.status != 200) err = sdca.service + ' (' + sdca.idos + ' status = ' + xhr.status + ')';
	else if (xhr.responseText === undefined) err = sdca.service + ': undefined ' + sdca.idos + ' response';
	else if (xhr.responseText === null) err = sdca.service + ': null ' + sdca.idos + ' response';

	if (!err) return xhr.responseText;

	Client.fyi.epano(err);
	return err;

}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Η μέθοδος "protipo" δέχεται κάποιον jQuery selector με τον οποίον
// εντοπίζει κάποιο DOM element και αντιγράφει τον σκελετό αυτού του
// DOM element στα στοιχεία της jQuery list επί της οποίας εφαρμόζεται.

jQuery.fn.protipo = function(selector) {
	var html = $(selector).akrivosEna().html();
	return this.each(function() {
		$(this).html(html);
	});
}

// Η μέθοδος "paraskinio" αλλάζει το background των selected items. Δέχεται ως
// παράμετρο το όνομα του αρχείου εικόνας στο directory "images/paraskinio".

jQuery.fn.paraskinio = function(img) {
	if (!img) img = Client.session.paraskinio;
	return this.each(function() {
		$(this).css({backgroundImage: 'url(' + Client.server + 'images/paraskinio/' + img + ')'});
	});
}

// Η μέθοδος "formaLathos" χρησιμοποιείται για να χρωματίσει κατάλληλα
// κάποια input πεδία που έχουν λανθασμένες τιμές. Εάν περάσουμε true
// παράμετρο το πεδίο χρωματίζεται (λάθος), αλλιώς αποχρωματίζεται.

jQuery.fn.formaLathos = function(err) {
	return this.each(function() {
		if (err) $(this).addClass('formaLathos');
		else $(this).removeClass('formaLathos');
	});
}

// Η μέθοδος "working" εφαρμόζεται σε εικόνες και τις αντικαθιστά με
// εικόνες εκτέλεσης εργασιών. Είναι χρήσιμη κυρίως κατά το κλικ σε
// εργαλεία όπου αλλάζουμε προσωρινά την εικόνα με εικόνα εκτέλεσης
// εργασιών μέχρι να ολοκληρωθεί η σχετική εργασία.

jQuery.fn.working = function(wrk) {
	return this.each(function() {
		$(this).attr({src: Client.server + 'images/' + (wrk ? wrk : 'working/default.gif')});
	});
}

// Η μέθοδος "anadisi" προκαλεί ανάδυση του στοιχείου μέσω αύξησης
// του σχετικού z-index.

jQuery.fn.anadisi = function() {
	return this.each(function() {
		$(this).css({zIndex: Client.anadisiIndex++});
	});
}

// Η μέθοδος "jqsel" δέχεται ως παράμετρο ένα string και το μετατρέπει σε μορφή
// ασφαλή ώστε να χρησιμοποιηθεί μέσα σε jQuery selectors.

Client.jqsel = function(s) {
	return s.replace(/([^a-zA-Z0-9_])/g, '\\$1');
}

String.prototype.jqsel = function(s) {
	return Client.jqsel(this);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Η μέθοδος "siromeno" δημιουργεί δυνατότητα μετακίνησης στα στοιχεία στα οποία
// εφαρμόζεται. Μπορούμε να περάσουμε ως παράμετρο css object με διάφορα στιλιστικά
// χαρακτηριστικά τα οποία θα εφαρμοστούν στο συρόμενο στοιχείο, π.χ. "top", "left"
// κλπ. Τα συρόμενα στοιχεία πρέπει να είναι absolute position elements και αν δεν
// είναι μετατρέπονται αυτόματα σε τέτοια. Αφαιρείται επίσης αυτόματα το wrap σε
// λευκούς χαρακτήρες, εκτός και αν υπάρχει άλλη σχετική προδιαγραφή στο στοιχείο.
//
// Αν περάσουμε παράμετρο "false" τότε ακυρώνονται οι χειρισμοί μετακίνησης για τα
// στοιχεία της λίστας. Εφαρμόζεται συνήθως σε υποστοιχεία του συρόμενου στοιχείου,
// π.χ. σε links, buttons κλπ. στα οποία θέλουμε να εκτελούμε άλλες εργασίες με το
// pointing device.

jQuery.fn.siromeno = function(css) {
	return this.each(function() {
		var obj = $(this);

		if (css === false) {
			obj.on('mousedown mousemove mouseup', function(e) {
				e.stopPropagation();
			});
			return;
		}

		// Θέτουμε κάποια στιλιστικά χαρακτηριστικά του στοιχείου
		// που είναι χρήσιμα στη μετακίνηση.
		obj.addClass('siromeno');
		if (!css) css = {};
		css.position = 'absolute';
		if (!obj.css('whiteSpace')) css.whiteSpace = 'nowrap';
		obj.css(css);

		// Θέτουμε το στοιχείο top/bottom εφόσον δεν υπάρχει.
		var siromeno_t = parseInt(obj.css('top'));
		if (isNaN(siromeno_t)) {
			var siromeno_b = parseInt(obj.css('bottom'));
			if (isNaN(siromeno_b)) obj.css({top: 0});
		}

		// Θέτουμε το στοιχείο left/right εφόσον δεν υπάρχει.
		var siromeno_l = parseInt(obj.css('left'));
		if (isNaN(siromeno_l)) {
			var siromeno_r = parseInt(obj.css('right'));
			if (isNaN(siromeno_r)) obj.css({left: 0});
		}

		$(this).find("input").on('mousedown', function(e) {
			e.stopPropagation();
		});

		var cursor = obj.css('cursor');
		var opacity = obj.css('opacity');
		var text = $();

		obj.on('click', function(e) {
			e.stopPropagation();
		}).on('mouseenter', function(e) {
			e.stopPropagation();
			obj.css({cursor: 'crosshair'});
		}).on('mouseleave', function(e) {
			obj.css({cursor: cursor ? cursor : 'auto'});
		}).on('mousedown', function(e) {
			e.stopPropagation();
			e.preventDefault();

			var siromeno_x = e.pageX;
			var siromeno_y = e.pageY;

			siromeno_t = parseInt(obj.css('top'));
			if (isNaN(siromeno_t)) {
				siromeno_b = parseInt(obj.css('bottom'));
				if (isNaN(siromeno_b)) {
					obj.css({top: 0});
					siromeno_t = 0;
					siromeno_b = null;
				}
				else {
					siromeno_t = null;
				}
			}

			siromeno_l = parseInt(obj.css('left'));
			if (isNaN(siromeno_l)) {
				siromeno_r = parseInt(obj.css('right'));
				if (isNaN(siromeno_r)) {
					obj.css({left: 0});
					siromeno_l = 0;
					siromeno_r = null;
				}
				else {
					siromeno_l = null;
				}
			}

			// Αλλάζουμε τον κέρσορα και προσδίδουμε διαφάνεια
			// στο συρόμενο στοιχείο.
			obj.css({cursor: 'move'}).anadisi();
			if (opacity > 0.9) obj.fadeTo(100, opacity - 0.3);
			else opacity = false;

			// Ο παρακάτω έλεγχος αποσωβεί δυσάρεστη κατάσταση σε γρήγορο mousemove
			// με mouseup πάνω σε input button κλπ.
			if (text.length) text.prop({disabled: false});
			text = $(this).find('input:enabled,textarea:enabled').prop({disabled: true}).off('mousemove');

			var arot = globals.torams();
			var moving = true;

			obj.find('*').not('marquee').on('scroll', function(e) {
				e.stopPropagation();
				obj.trigger('mouseup');
			});

			$(document).on('mousemove', function(e) {
				e.stopPropagation();
				e.preventDefault();

				if (!moving) return;

				var tora = globals.torams();
				if (tora - arot < 40) return;

				var dy = e.pageY; if (dy < 10) dy = 10; dy -= siromeno_y;
				var dx = e.pageX; if (dx < 10) dx = 10; dx -= siromeno_x;

				if (siromeno_t !== null) obj.css({top: (siromeno_t + dy) + 'px'});
				else obj.css({bottom: (siromeno_b - dy) + 'px'});

				if (siromeno_l !== null) obj.css({left: (siromeno_l + dx) + 'px'});
				else obj.css({right: (siromeno_r - dx) + 'px'});
				arot = tora;
			}).on('mouseup', function(e) {
				e.stopPropagation();
				e.preventDefault();

				moving = false;
				$(document).off('mousemove mouseup contextmenu');
				text.prop({disabled: false}).off('mousemove');

				var css = {};
				css.cursor = 'crosshair';
				if (opacity !== false) css.opacity = opacity;
				obj.stop().css(css);
			}).on('contextmenu', function(e) {
				e.stopPropagation();
				e.preventDefault();
			});
		});
	});
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Client.pineza = {};

Client.pineza.id = 0;

Client.pineza.timer = {};

Client.pineza.callback = {};

Client.pineza.karfitsoma = function(e, ico) {
	Client.stopProp(e);
	$(ico).attr('onclick', '').unbind('click').attr({src: Client.server + 'images/misc/karfitsoma.png'});
	var panel = $(ico).parent();
	var id = panel.attr('id');
	var obj = panel.parent().stop().fadeTo(100, 1);
	setTimeout(function() {
		$(ico).fadeOut(500, function() {
			$(this).remove();
		});
	}, 4000);

	clearTimeout(Client.pineza.timer[id]);
	delete Client.pineza.timer[id];
}

Client.pineza.klisimo = function(e, ico) {
	Client.stopProp(e);
	var panel = $(ico).parent();
	var id = panel.attr('id');
	var obj = panel.parent().stop();
	panel.find('.pinezaPineza').remove();

	clearTimeout(Client.pineza.timer[id]);
	delete Client.pineza.timer[id];

	if (Client.pineza.callback[id]) Client.pineza.callback[id](obj);
	else obj.fadeOut(300);
}

// Αν θέλουμε να διαγράψουμε ένα κλεινόμενο στοιχείο, αυτό το κάνουμε
// με την "destroy", ακόμη και μέσα από callback function κλεισίματος.

Client.pineza.destroy = function(obj) {
	if (!obj.length) return;

	var id = obj.find('.pinezaPanel').attr('id');
	delete Client.pineza.timer[id]
	delete Client.pineza.callback[id];
	obj.remove();
}

jQuery.fn.pineza = function() {
	var hideDelay = null;
	var callback = null;
	var css = null;

	for (var i in arguments) {
		switch (typeof arguments[i]) {
		case 'number':
			hideDelay = parseInt(arguments[i]);
			break;
		case 'function':
			callback = arguments[i];
			break;
		case 'object':
			css = arguments[i];
			break;
		default:
			globals.fatal('pineza: invalid argument');
		}
	}

	if (!css) css = {};
	css.position = 'absolute';
	if ((!css.hasOwnProperty('top')) && (!css.hasOwnProperty('bottom'))) css.top = 0;
	if ((!css.hasOwnProperty('right')) && (!css.hasOwnProperty('left'))) css.right = 0;

	return this.each(function() {
		var id = 'PNZ' + (++Client.pineza.id);
		var html = '<div id="' + id + '" class="pinezaPanel">';
		if (hideDelay) html += '<img class="pinezaIcon pinezaPineza" title="Καρφίτσωμα" src="' +
			Client.server + '/images/misc/pineza.png" ' +
			'onclick="Client.pineza.karfitsoma(event, this);" />';
		html += '<img class="pinezaIcon pinezaKlisimo" title="Κλείσιμο" src="' +
			Client.server + '/images/misc/klisimo.png" ' +
			'onclick="Client.pineza.klisimo(event, this);" />';
		html += '</div>';

		var obj = $(this);

		$(html).fadeTo(100, 0.1).on('mouseenter', function() {
			$(this).fadeTo(100, 1);
		}).on('mouseleave', function() {
			$(this).fadeTo(100, 0.1);
		}).on('mousedown', function(e) {
			e.stopPropagation();
			e.preventDefault();
		}).appendTo(obj).css(css).siromeno(false);

		if (hideDelay) Client.pineza.timer[id] = setTimeout(function() {
			obj.fadeOut(4000, function() {
				obj.find('.pinezaPineza').remove();
				if (Client.pineza.callback[id]) callback(obj);
			});
			delete Client.pineza.timer[id];
		}, hideDelay);

		if (callback) Client.pineza.callback[id] = callback;
	});
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Οι παρακάτω μέθοδοι χρησιμοποιούνται για να κάνουν εμφανές κάποιο σημείο
// της σελίδας το οποίο ο χρήστης πρέπει να δει ή να κάνει κλικ κλπ. Η μέθοδος
// "edoEndixi" είναι η πιο γενική από αυτές και δέχεται ως παραμέτρους τα εξής:
// Το όνομα ενός gif image file από το directory "images/endixi" και προαιρετικά
// ένα css object με τα επιθυμητά χαρακτηριστικά της εικόνας. Προαιρετικά, επίσης,
// μπορούμε να περάσουμε και ένα χρονικό διάστημα σε milliseconds μετά το οποίοα
// θα σβηστεί η ένδειξη.

jQuery.fn.edoEndixi = function() {
	var timeout = 0;
	for (var i = 0; i < arguments.length; i++) {
		switch (typeof arguments[i]) {
		case 'string':
			var img = arguments[i];
			break;
		case 'object':
			var css = arguments[i];
			break;
		case 'number':
			var timeout = parseInt(arguments[i]);
			break;
		}
	}

	if (!img) img = 'svisimo';
	if (!css) css = {};

	return this.each(function() {
		var obj = $(this);
		switch (obj.css('position')) {
		case 'absolute':
		case 'relative':
			break;
		default:
			obj.css({position: 'relative'});
			break;
		}

		obj.children('.edoEndixi').remove();
		var x = $('<img class="edoEndixi" src="' + Client.server + 'images/endixi/' + img + '.gif" />').
			css(css).prependTo(obj);

		if (timeout > 0) setTimeout(function() {
			x.fadeOut(function() {
				$(this).remove();
			});
		}, timeout);
	});
}

jQuery.fn.edoKita = function() {
	var timeout = 0;
	for (var i = 0; i < arguments.length; i++) {
		switch (typeof arguments[i]) {
		case 'object':
			var css = arguments[i];
			break;
		case 'number':
			timeout = parseInt(arguments[i]);
			break;
		}
	}

	if (!css) css = {};
	if (!css.hasOwnProperty('width')) css.width = '48px';
	if (!css.hasOwnProperty('left')) css.left = '10px';
	if (!css.hasOwnProperty('top')) css.top = '-24px';
	return this.edoEndixi('edoKita', css, timeout);
}

jQuery.fn.edoPata = function() {
	var timeout = 0;
	for (var i = 0; i < arguments.length; i++) {
		switch (typeof arguments[i]) {
		case 'object':
			var css = arguments[i];
			break;
		case 'number':
			timeout = parseInt(arguments[i]);
			break;
		}
	}

	if (!css) css = {};
	if (!css.hasOwnProperty('width')) css.width = '36px';
	if (!css.hasOwnProperty('left')) css.left = '4px';
	if (!css.hasOwnProperty('top')) css.top = '-24px';
	return this.edoEndixi('edoPata', css, timeout);
}

// Η μέθοδος "zevra" εφαρμόζεται στα παιδιά κάποιου DOM element και δημιουργεί
// το zebra εφέ, χρωματίζοντας λίγο διαφορετικά τα μονά από τα ζυγά items.
// Το όνομα επελέγη "zevra" και όχι "zebra" όπως ίσως θα ήταν πιο σωστό, με το
// σκεπτικό μελλοντικής μεθόδου "zebra" στο jQuery.

jQuery.fn.zevra = function(rgb1, rgb2) {
	switch (arguments.length) {
	case 0:
		rgb1 = '#FFFF99';
		rgb2 = '#FFFFB8';
		break;
	case 2:
		break;
	default:
		globals.fatal('jQuery.zevra: invalid arguments');
	}

	$(this).children().each(function() {
		$(this).css({backgroundColor: $(this).index() % 2 ? rgb2 : rgb1});
	});
}

// Η μέθοδος "dialogosButton" εφαρμόζεται σε κουμπιά διαλόγων στα οποία προσδίδουν
// οντότητα και λειτουργικότητα. Ως πρώτη παράμετρο περνάμε το κείμενο του κουμπιού,
// και ακολουθεί callback function η οποία καλείται κατά το κλικ στο κουμπί. Κατά
// το κλικ διαγράφεται αυτόματα από το DOM και το ίδιο το πλαίσιο διαλόγου (parent
// element κλάσης "dialogos") εκτός και αν περαστεί τρίτη παράμετρος true.

jQuery.fn.dialogosButton = function(text, callback, keep) {
	return this.each(function() {
		$(this).addClass('dialogosButton').text(text).
		on('mousedown', function(e) {
			e.stopPropagation();
		}).
		on('click', function(e) {
			Prefadoros.input.apiko(e);
			if (callback) callback();
			if (keep) return;
			$(this).parents('.dialogos').fadeOut(100, function() {
				$(this).remove();
			});
		});
	});
}

jQuery.fn.estoEna = function() {
	if (this.length < 1) globals.fatal('δεν βρέθηκαν στοιχεία στο DOM');
	return this;
}

jQuery.fn.topoliEna = function() {
	if (this.length > 1) globals.fatal('βρέθηκαν περισσότερα από ένα στοιχεία στο DOM');
	return this;
}

jQuery.fn.akrivosEna = function() {
	if (this.length < 1) globals.fatal('δεν βρέθηκαν στοιχεία στο DOM');
	if (this.length > 1) globals.fatal('βρέθηκαν πολλά στοιχεία στο DOM');
	return this;
}

jQuery.fn.kanena = function() {
	if (this.length > 0) globals.fatal('βρέθηκαν στοιχεία στο DOM');
	return this;
}

jQuery.fn.endixi = function() {
	var endixi = 'images/endixi/svisimo.gif';
	var delay = 1000;

	for (var i = 0; i < arguments.length; i++) {
		if ((typeof arguments[i]) == 'string') endixi = arguments[i];
		else if ((typeof arguments[i]) == 'number') delay = parseInt(arguments[i]);
	}

	return this.each(function() {
		var img = $(this);
		var src = img.attr('src');
		if (src == endixi) return;

		img.attr({'src': endixi});
		setTimeout(function() {
			img.attr({'src': src});
		}, delay);
	});
}

jQuery.fn.strofi = function(arxiki, gonia, duration, easing, complete){
    return this.each(function(){
        var elem = $(this);

        $({deg: arxiki}).animate({deg: gonia}, {
            duration: duration,
            easing: easing,
            step: function(now){
                elem.css({
                    transform: "rotate(" + now + "deg)"
                });
            },
            complete: complete || $.noop,
        });
    });
};

// Η jQuery μέθοδος "scrollKato" εφαρμόζεται σε scrollable DOM elements και
// προκαλεί scroll bottom στα συγκεκριμένα elements.

jQuery.fn.scrollKato = function(slow) {
	return this.each(function() {
		try {
			if (slow) $(this).animate({scrollTop: this.scrollHeight}, 'slow');
			else this.scrollTop = this.scrollHeight;
		} catch(e) {};
	});
}

// Η function "getCssTimi" δέχεται μια κλάση CSS και το όνομα ενός ή περισσοτέρων style
// attributes, και επιστρέφει αντικείμενο δεικτοδοτημένο με τα attributes που περάστηκαν
// και τιμές τις τιμές των συγκεκριμένων attributes στη συγκεκριμένη κλάση. Η κλάση μπορεί
// να είναι και σύνθετη, π.χ. "pektis pektisOffline", οπότε θα επιστραφούν οι τελικές τιμές
// των style attributes.
//
// Τα ζητούμενα attributes περνάνε όπως ακριβώς και στην "jQuery.css()", δηλαδή εαν πρόκειται
// για ένα attribute περνάει ως string και επιστρέφεται η αντίστοιχη τιμή, ενώ περισσότερα
// attributes περνούν ως array και επιστρέφεται αντικείμενο με properties τα ζητούμενα
// attributes και τιμές τις τιμές των αντίστοιχων style attributes.

Client.getCssTimi = function(klasi, attrs) {
	var tmp = $('<div>').hide().addClass(klasi);
	$(document.body).append(tmp);
	try {
		return tmp.css(attrs);
	} finally {
		tmp.remove();
	}
}

jQuery.fn.kokino = function(xroma) {
	return this.each(function() {
		$(this).css({backgroundColor: xroma ? xroma : 'red'});
	});
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Client.provlima = function(err, w, div, cb) {
	$('.provlima').remove();
	if (!div) div = $('#ofelimo');
	if (!div.length) div = $(document.body);
	if (!div.length) throw new Error(err);

	var p = $('<div class="provlima"></div>').appendTo($(div));
	var i = $('<img class="provlimaIcon" src="' + Client.server + 'images/misc/provlima.png" />').appendTo(p);
	var k = $('<div class="provlimaKimeno"></div>').appendTo(p).html(err.replace(/\n/g, '<br />'));

	if (!w) k.css({whiteSpace: 'nowrap'})
	else if (w) {
		if (parseInt(w) == w) w += 'px';
		k.css({width: w});
	}

	i.animate({width: '80px'}, 400, 'easeInCirc', function() {
		Client.sound.error();
		k.fadeIn(100, function() {
			k.animate({bottom: '+=10px', left: '+=10px'}, 600, 'easeOutBounce');
		});
		if (cb) cb();
	});
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Client.setup1 = function() {
	$(document.body).paraskinio();
	if (!window.URL) window.URL = window.webkitURL;

	$('#toolbar').children('table').css({width: '100%'});
	$('#toolbar').fadeIn();

	$('#ribbon').children('table').css({width: '100%'});
	$('#ribbon').fadeIn();

	$(document).
	on('mouseenter', '.iconButton', function() { $(this).addClass('iconButtonOplisate'); }).
	on('mouseleave', '.iconButton', function() { $(this).removeClass('iconButtonOplisate'); }).
	on('mouseenter', '.formaButton', function() { $(this).addClass('formaButtonOplisate'); }).
	on('mouseleave', '.formaButton', function() { $(this).removeClass('formaButtonOplisate'); }).
	on('mouseenter', '.dialogosButton', function() { $(this).addClass('dialogosButtonOplisate'); }).
	on('mouseleave', '.dialogosButton', function() { $(this).removeClass('dialogosButtonOplisate'); }).
	on('focus', '.iconButton', function() { $(this).addClass('iconButtonOplisate'); }).
	on('blur', '.iconButton', function() { $(this).removeClass('iconButtonOplisate'); });

	$('.tab,#ribbonRight').addClass('kimeno');

	Client.loginPhotoSetup();
}

Client.loginPhotoSetup = function() {
	if (Client.oxiPektis()) return;

	$('.loginPhoto').on('error', function() {
		$(this).attr({src: Client.server + 'images/misc/provlima.gif'});
	});
	$('#loginPhotoContainer').find('.loginPhoto').on('click', function() {
		var cont = $('#loginPhotoContainer');
		if (cont.find('.loginPhotoKadro').length) {
			cont.find('.loginPhoto').stop().animate({width: '22px'}, 270, function() {
				$(this).removeClass('loginPhotoKadro').attr({
					src: Client.missingPhoto,
					title: 'Φωτογραφία παίκτη'
				});
			});
		}
		else {
			cont.find('.loginPhoto').addClass('loginPhotoKadro').anadisi().
			attr({
				src: Client.pektisPhotofile(Client.session.pektis),
				title: "Απόκρυψη",
			}).stop().animate({width: Client.photoPektisWidth}, 270);
		}
	});
}

Client.setup2 = function() {
	// Κάθε 4-5 λεπτά ανανεώνουμε το session.

	setInterval(function() {
		var msg = 'session recharged';
		Client.ajax('misc/sessionAlive', function(rsp) {
			if (rsp) msg += ' (' + rsp + ')';
			Client.fyi.pano(msg);
			console.log(msg);
		});
	}, globals.random(24000, 30000));
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

$(document).ready(function() {
	Client.setup1();
});

// Λίγο αργότερα…

$(window).load(function() {
	Client.setup2();
});
